package com.ctrip.corp.obt.shard.utils;

import java.lang.reflect.Field;

import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;

import com.ctrip.corp.obt.generic.utils.ReflectionUtils;
import com.ctrip.corp.obt.shard.sql.parser.IPluginPropertyParserRequest;
import com.ctrip.corp.obt.shard.utils.PluginUtils.DSBoundSql;

import lombok.extern.slf4j.Slf4j;

/**
 * 预编译参数解析工具类
 * 
 * @author xukang zhang
 * @date 2024/3/8 13:53
 */
@Slf4j
public class ParameterPropertyUtils {

    /**
     * get the property value of the corresponding parameterMapping according to the parameterObject
     *
     * @param request
     * @param parameterMapping
     * @param parameterObject
     * @return
     */
    public static Object getPropertyValue(IPluginPropertyParserRequest request,
                                          ParameterMapping parameterMapping,
                                          Object parameterObject) {
        if (!IPluginPropertyParserRequest.validate(request)) {
            return null;
        }
        if (parameterMapping == null) {
            return null;
        }
        if (parameterMapping.getMode() == ParameterMode.OUT) {
            return null;
        }
        BoundSql boundSql = request.getBoundSql().boundSql();
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
            // Additional Parameter for dynamic sql binding (eg: forEach)
            value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
            value = null;
        } else if (parameterObject instanceof String) {
            // if String, then directly use it
            value = parameterObject;
        } else {
            MetaObject metaObject = request.getConfiguration().newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
        }
        return value;
    }

    /**
     * get the located Field of corresponding property
     *
     * eg: parameterMapping.getProperty() = "a.b.c", parameterObject = {a: {b: {c: 1}}}, then return {b: {c: 1}}
     *
     * @param parameterMapping
     * @param parameterObject
     * @return
     */
    public static Field getPropertyLocatedField(IPluginPropertyParserRequest request,
        ParameterMapping parameterMapping,
        Object parameterObject) {
        if (!IPluginPropertyParserRequest.validate(request)) {
            return null;
        }
        if (parameterObject == null) {
            // if parameter is null, no need to get Property
            return null;
        } else if (parameterObject instanceof String) {
            // if parameter is String, no need to get Property
            return null;
        }
        if (parameterMapping == null) {
            return null;
        }
        String propertyName = parameterMapping.getProperty();
        int delimiter = propertyName.lastIndexOf('.');
        if (delimiter == -1) {
            // no child property, then return the parameterObject itself
            return getFieldFromObject(parameterObject, propertyName);
        }
        BoundSql boundSql = request.getBoundSql().boundSql();
        Field field;
        String lastButOnePropertyName = propertyName.substring(0, delimiter);
        String innerPropertyName = propertyName.substring(delimiter + 1);
        if (boundSql.hasAdditionalParameter(lastButOnePropertyName)) {
            // Additional Parameter for dynamic sql binding (eg: forEach)
            field = getFieldFromObject(boundSql.getAdditionalParameter(lastButOnePropertyName), innerPropertyName);
        } else {
            // Otherwise, use MetaObject to get the property field
            MetaObject metaObject = request.getConfiguration().newMetaObject(parameterObject);
            field = getFieldFromObject(metaObject.getValue(lastButOnePropertyName), innerPropertyName);
        }
        return field;
    }

    private static boolean invalidParameters(Configuration configuration, DSBoundSql boundSql,
        ParameterMapping parameterMapping) {
        if (configuration == null) {
            return true;
        }
        if (boundSql == null) {
            return true;
        }
        if (parameterMapping == null) {
            return true;
        }
        if (parameterMapping.getMode() == ParameterMode.OUT) {
            return true;
        }
        return false;
    }

    private static Field getFieldFromObject(Object object, String propertyName) {
        if (object == null) {
            return null;
        }
        try {
            return ReflectionUtils.findField(object.getClass(), propertyName);
        } catch (Exception e) {
            log.error("[ParameterPropertyUtils:getFieldFromObject failed with error]", e);
            return null;
        }
    }

}
